/*
 * anima/common/acpi.cc - Functions for locating ACPI tables.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#include HEADER (anima/common,acpi.hh)
#include HEADER (anima,firmware.hh)
#include HEADER (anima,console.hh)
#include HEADER (utils,memory.hh)
#include HEADER (utils,minmax.hh)

START_NAMESPACE2 (anima,common)

const ::acpi::rsdp_t *acpi::rsdp;

static const ::acpi::rsdt_t *rsdt = (const ::acpi::rsdt_t *) U64 (-1);
static const ::acpi::xsdt_t *xsdt = (const ::acpi::xsdt_t *) U64 (-1);

static bool
tbl_valid_p (u64_t addr, u32_t sig, bool chksum_p)
{
  firmware::region_t region;
  return firmware::region_t::find (addr, region)
         && firmware::region_t::acpi_p (region.type)
         && utils::in_range_p (addr + sizeof (::acpi::sdt_t),
                               region.addr, region.addr + region.size)
         && utils::in_range_p (addr + ((const ::acpi::sdt_t *) addr)->len,
                               region.addr, region.addr + region.size)
         && ((const ::acpi::sdt_t *) addr)->sig == sig
         && (!chksum_p || ((const ::acpi::sdt_t *) addr)->valid_p ());
}

bool
acpi::init (u64_t start, u64_t end)
{
  firmware::region_t region;
  bool found_rsdp_p = false;

  for (const void *prev = firmware::region_t::next (NULL, region);
       prev; prev = firmware::region_t::next (prev, region))
    {
      if (!firmware::region_t::acpi_p (region.type)
          || (region.addr + region.size) <= start
          || (end == 0 && region.addr > start)
          || (end != 0 && region.addr >= end))
        continue;

      for (const void *ptr
             = (const void *) utils::max (start, region.addr);
           (u64_t) ptr <= utils::min (
                            end ? end : utils::max (start, region.addr),
                            region.addr + region.size)
                          - (end ? offsetof (::acpi::rsdp_t, len) : 0);
           ++ptr)
        {
          if (*((const u64_t *) ptr) == ::acpi::rsdp_t::SIG
              && (((const ::acpi::rsdp_t *) ptr)->rev == 0
                  || ((u64_t) ptr + ((const ::acpi::rsdp_t *) ptr)->len)
                     <= region.addr + region.size)
              && ((const ::acpi::rsdp_t *) ptr)->valid_p ())
            {
              rsdp = (const ::acpi::rsdp_t *) ptr;
              found_rsdp_p = true;
              break;
            }
        }

      if (found_rsdp_p)
        break;
    }

  if (!found_rsdp_p)
    return false;

  if (rsdp->rev >= 1
      && tbl_valid_p (rsdp->xsdt_addr, ::acpi::xsdt_t::SIG, false))
    {
      if (!((const ::acpi::xsdt_t *) rsdp->xsdt_addr)->valid_p ())
        console::warning ("ACPI table XSDT is corrupted.");
      else
        xsdt = (const ::acpi::xsdt_t *) rsdp->xsdt_addr;
    }

  if (tbl_valid_p (rsdp->rsdt_addr, ::acpi::rsdt_t::SIG, false))
    {
      if (!((const ::acpi::rsdt_t *) rsdp->rsdt_addr)->valid_p ())
        console::warning ("ACPI table RSDT is corrupted.");
      else
        rsdt = (const ::acpi::rsdt_t *) rsdp->rsdt_addr;
    }

  return (u64_t) rsdt != U64 (-1) || (u64_t) xsdt != U64 (-1);
}

bool
acpi::find (u32_t sig, const ::acpi::sdt_t *&tbl)
{
  if ((u64_t) xsdt != U64 (-1))
    for (u32_t i = 0;
         i < (xsdt->len - sizeof (::acpi::xsdt_t)) / sizeof (u64_t); ++i)
      {
        if (tbl_valid_p (xsdt->entries[i], sig, true))
          {
            tbl = (const ::acpi::sdt_t *) xsdt->entries[i];
            return true;
          }
      }

  if ((u64_t) rsdt != U64 (-1))
    for (u32_t i = 0;
         i < (rsdt->len - sizeof (::acpi::rsdt_t)) / sizeof (u32_t); ++i)
      {
        if (tbl_valid_p (rsdt->entries[i], sig, true))
          {
            tbl = (const ::acpi::sdt_t *) (u64_t) rsdt->entries[i];
            return true;
          }
      }

  return false;
}

END_NAMESPACE2

